#pragma once
#include "KMeansStandardV1_Util.h"
#include "KMeansStandardV1NonConcurrent.h"
#include "OpenClTemplate.h"
#include "Logger.h"

#include <math.h>

namespace Algorithm
{

template<unsigned int OBSERVATION_VECTOR_SIZE, unsigned int NUMBER_OF_OBSERVATIONS, unsigned int NUMBER_OF_CENTROIDS >
class KMeansStandardV1ConcurrentV1
{
public:
	typedef KMeansStandardV1_Util<OBSERVATION_VECTOR_SIZE, NUMBER_OF_OBSERVATIONS, NUMBER_OF_CENTROIDS > SupportedKMeansStandardV1_Util;
	typedef KMeansStandardV1NonConcurrent<OBSERVATION_VECTOR_SIZE, NUMBER_OF_OBSERVATIONS, NUMBER_OF_CENTROIDS > SupportedKMeansStandardV1NonConcurrent;

	static void algorithm(const typename SupportedKMeansStandardV1_Util::TSpAlgorithmData& _algorithmData)
	{
		std::string buildOptions_;
		{ // create preprocessor defines for the kernel
		  char buf[256]; 
		  sprintf_s(buf,"-D OBSERVATION_VECTOR_SIZE=%d -D NUMBER_OF_OBSERVATIONS=%d -D NUMBER_OF_CENTROIDS=%d",
			  OBSERVATION_VECTOR_SIZE, NUMBER_OF_OBSERVATIONS,NUMBER_OF_CENTROIDS);
		  buildOptions_ += std::string(buf);
		}

		OpenClTemplate::SharedPtr spOpenClTemplate=
			OpenClTemplate::constructDefault("KMeansStandardV1ConcurrentV1.cl",buildOptions_);
		cl::Kernel kernelFindClosestCentroid
			=spOpenClTemplate->buildKernel("findClosestCentroid");;

		int loop=0;
		for(loop=0;true;loop++)
		{
			findClosestCentroid(_algorithmData,spOpenClTemplate,kernelFindClosestCentroid);
			calculateNewCentroids(_algorithmData);
			checkConvergence(_algorithmData);
			if(_algorithmData->convergence)break;
		}
		Logger::getOStream()<<std::endl<<"Loops="<<loop;
	}

	static void findClosestCentroid(const typename SupportedKMeansStandardV1_Util::TSpAlgorithmData& _algorithmData,
		OpenClTemplate::SharedPtr& _spOpenClTemplate,
		cl::Kernel& _kernel)
	{
		cl::Buffer bufferObservations_(
			_spOpenClTemplate->context, 
            CL_MEM_READ_ONLY | CL_MEM_USE_HOST_PTR,
            sizeof(_algorithmData->observations.data),
            _algorithmData->observations.data);

        cl::Buffer bufferCurrentCentroids_(
			_spOpenClTemplate->context, 
            CL_MEM_READ_ONLY | CL_MEM_USE_HOST_PTR,
            sizeof(_algorithmData->currentCentroids.data),
            _algorithmData->currentCentroids.data);

        cl::Buffer bufferClosestCentroid_(
			_spOpenClTemplate->context, 
            CL_MEM_WRITE_ONLY | CL_MEM_USE_HOST_PTR,
            sizeof(_algorithmData->closestCentroid.data),
            _algorithmData->closestCentroid.data);

		_kernel.setArg(0, bufferObservations_);
		_kernel.setArg(1, bufferCurrentCentroids_);
		_kernel.setArg(2, bufferClosestCentroid_);

		cl::Event event;

		const size_t WORK_GROUP_SIZE_=32;

		_spOpenClTemplate->queue.enqueueNDRangeKernel
			(
			_kernel,
			cl::NullRange,
			cl::NDRange(NUMBER_OF_OBSERVATIONS+(WORK_GROUP_SIZE_-NUMBER_OF_OBSERVATIONS%WORK_GROUP_SIZE_)),//globalThreads
			cl::NDRange(WORK_GROUP_SIZE_),//localThreads
			NULL,
			&event);

		_spOpenClTemplate->waitForKernelExecution(event);

		_spOpenClTemplate
			->queue.enqueueReadBuffer(
			bufferClosestCentroid_,
			CL_TRUE,
			0,
			sizeof(_algorithmData->closestCentroid.data),
            _algorithmData->closestCentroid.data);

	}

	static void calculateNewCentroids(const typename SupportedKMeansStandardV1_Util::TSpAlgorithmData& _algorithmData)
	{
		SupportedKMeansStandardV1NonConcurrent::calculateNewCentroids(_algorithmData);
	}

	static void checkConvergence(const typename SupportedKMeansStandardV1_Util::TSpAlgorithmData& _algorithmData)
	{
		SupportedKMeansStandardV1NonConcurrent::checkConvergence(_algorithmData);
	}



	static void debugingFunction1()
	{

		std::string buildOptions_;
		{ // create preprocessor defines for the kernel
		  char buf[256]; 
		  sprintf_s(buf,"-D OBSERVATION_VECTOR_SIZE=%d -D NUMBER_OF_OBSERVATIONS=%d -D NUMBER_OF_CENTROIDS=%d",
			  OBSERVATION_VECTOR_SIZE, NUMBER_OF_OBSERVATIONS,NUMBER_OF_CENTROIDS);
		  buildOptions_ += std::string(buf);
		}
		

		OpenClTemplate::SharedPtr spOpenClTemplate=
			OpenClTemplate::constructDefault("KMeansStandardV1ConcurrentV1.cl",buildOptions_);
		cl::Kernel kernelFindClosestCentroid
			=spOpenClTemplate->buildKernel("findClosestCentroid");;


			

		for(int i=0;true;i++)
		{
			Logger::getOStream() << std::endl<<"debugingFunction1 loop " << i;

			SupportedKMeansStandardV1_Util::TSpAlgorithmData inputAlgorithmData
				=SupportedKMeansStandardV1_Util::GenerateAlgorithmDataRandom::generate();
			SupportedKMeansStandardV1_Util::TSpAlgorithmData output1AlgorithmData(new SupportedKMeansStandardV1_Util::TAlgorithmData);
			SupportedKMeansStandardV1_Util::TSpAlgorithmData output2AlgorithmData(new SupportedKMeansStandardV1_Util::TAlgorithmData);
			*output1AlgorithmData=*inputAlgorithmData;
			*output2AlgorithmData=*inputAlgorithmData;

			SupportedKMeansStandardV1NonConcurrent::findClosestCentroid(output1AlgorithmData);
			findClosestCentroid(output2AlgorithmData,spOpenClTemplate,kernelFindClosestCentroid);


			if(!(output1AlgorithmData->closestCentroid==output2AlgorithmData->closestCentroid))
			{
				int i=0;
				i++;
			}

		}

	}


};

}//namespace Algorithm
